﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AnthStat.Domain
{
    public sealed class Measurement
    {
        #region Events
        public event EventHandler DateMeasurementUpdated;
        public event EventHandler ScoresUpdated;
        #endregion // Events

        #region Members
        private DateTime _dateMeasurement;
        private double _ageDays;
        private double? _height;
        private double? _weight;
        private double? _bmi;
        private double? _headCircumference;
        private double? _muac;
        private double? _ssf;
        private double? _tsf;
        private bool _isRecumbent = false;
        #endregion // Members

        #region Properties
        /// <summary>
        /// The child that made this visit
        /// </summary>
        public Person Person { get; private set; }

        /// <summary>
        /// The global record ID for this visit record
        /// </summary>
        public string GlobalRecordId { get; internal set; }

        /// <summary>
        /// The date the visit took place
        /// </summary>
        public DateTime DateMeasurement
        {
            get
            {
                return _dateMeasurement;
            }
            set
            {
                _dateMeasurement = value;
                RecalculateAge();
                RecalculateScores();

                if (DateMeasurementUpdated != null)
                {
                    DateMeasurementUpdated(this, new EventArgs());
                }
            }
        }

        /// <summary>
        /// Whether the date of measurement is estimated
        /// </summary>
        public bool IsDateMeasurementEstimated { get; set; }

        /// <summary>
        /// The date the measurement  record was created
        /// </summary>
        public DateTime DateCreated { get; set; }

        /// <summary>
        /// The date the measurement  record was updated
        /// </summary>
        public DateTime DateUpdated { get; set; }

        /// <summary>
        /// The child's age in days at the time of the visit
        /// </summary>
        public double AgeDays
        {
            get
            {
                return _ageDays;
            }
            set
            {
                _ageDays = Math.Round(value, 1);
                AgeMonths = Math.Round((AgeDays / 30.4368499), 2);
            }
        }

        /// <summary>
        /// The child's age in months at the time of the visit
        /// </summary>
        public double AgeMonths { get; private set; }

        /// <summary>
        /// The child's height as measured at the time of the visit
        /// </summary>
        public double? Height
        {
            get
            {
                return _height;
            }
            set
            {
                if (value.HasValue)
                {
                    _height = Math.Round(value.Value, 2);
                }
                else
                {
                    _height = null;
                }
            }
        }

        /// <summary>
        /// The child's weight as measured at the time of the visit
        /// </summary>
        public double? Weight
        {
            get
            {
                return _weight;
            }
            set
            {
                if (value.HasValue)
                {
                    _weight = Math.Round(value.Value, 2);
                }
                else
                {
                    _weight = null;
                }
            }
        }

        /// <summary>
        /// The child's head circumference as measured at the time of the visit
        /// </summary>
        public double? HeadCircumference
        {
            get
            {
                return _headCircumference;
            }
            set
            {
                if (value.HasValue)
                {
                    _headCircumference = Math.Round(value.Value, 2);
                }
                else
                {
                    _headCircumference = null;
                }
            }
        }

        /// <summary>
        /// The child's arm circumference as measured at the time of the visit
        /// </summary>
        public double? MidUpperArmCircumference
        {
            get
            {
                return _muac;
            }
            set
            {
                if (value.HasValue)
                {
                    _muac = Math.Round(value.Value, 2);
                }
                else
                {
                    _muac = null;
                }
            }
        }

        /// <summary>
        /// The child's triceps skin fold as measured at the time of the visit
        /// </summary>
        public double? TricepsSkinfold
        {
            get
            {
                return _tsf;
            }
            set
            {
                if (value.HasValue)
                {
                    _tsf = Math.Round(value.Value, 2);
                }
                else
                {
                    _tsf = null;
                }
            }
        }

        /// <summary>
        /// The child's subscapular skinfold as measured at the time of the visit
        /// </summary>
        public double? SubscapularSkinfold
        {
            get
            {
                return _ssf;
            }
            set
            {
                if (value.HasValue)
                {
                    _ssf = Math.Round(value.Value, 2);
                }
                else
                {
                    _ssf = null;
                }
            }
        }

        /// <summary>
        /// The child's BMI as measured at the time of the visit
        /// </summary>
        public double? BodyMassIndex
        {
            get
            {
                return _bmi;
            }
            internal set
            {
                if (value.HasValue)
                {
                    if (_bmi < 0)
                    {
                        throw new InvalidOperationException("Body mass index cannot be less than zero");
                    }

                    _bmi = Math.Round(value.Value, 2);
                }
                else
                {
                    _bmi = null;
                }
            }
        }

        /// <summary>
        /// Whether the child was recumbent at the time of the visit
        /// </summary>
        public bool IsRecumbent
        {
            get { return _isRecumbent; }
            set
            {
                _isRecumbent = value;
                RecalculateScores();
            }
        }

        /// <summary>
        /// Presence of oedema
        /// </summary>
        public bool HasOedema { get; set; }

        /// <summary>
        /// Any notes regarding this visit
        /// </summary>
        public string Notes { get; set; }

        /// <summary>
        /// The location of this visit
        /// </summary>
        public string Location { get; set; }

        /// <summary>
        /// Results (z-scores and percentiles) based on this measurement data using the CDC 2000 growth reference for individuals 2-20 years of age
        /// </summary>
        public ResultsCDC ResultsCDC { get; private set; }

        /// <summary>
        /// Results (z-scores and percentiles) based on this measurement data using the WHO 2006 and WHO 2007 growth references
        /// </summary>
        public ResultsWHO ResultsWHO { get; private set; }
        #endregion // Properties

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        public Measurement(string id, Person person, DateTime dateCreated, DateTime dateMeasurement)
        {
            Contract.Requires(!String.IsNullOrEmpty(id));
            Contract.Requires(id.Length == 36);
            Contract.Requires(dateCreated > new DateTime(1990, 1, 1));
            Contract.Requires(dateMeasurement > new DateTime(1900, 1, 1));
            Contract.Requires(person != null);

            GlobalRecordId = id;
            Person = person;
            DateCreated = dateCreated;
            DateMeasurement = dateMeasurement;
            Location = String.Empty;
            Notes = String.Empty;

            if (AgeMonths < 24)
            {
                IsRecumbent = true;
            }

            ResultsCDC cdc = new Domain.ResultsCDC(this);
            AddCDCResults(cdc);

            ResultsWHO who = new Domain.ResultsWHO(this);
            AddWHOResults(who);
        }
        #endregion // Constructors

        #region Methods

        internal IndicatorSet GetIndicatorSet()
        {
            return Person.GetIndicatorSet();
        }

        internal void AddCDCResults(ResultsCDC results)
        {
            Contract.Requires(results != null);
            Contract.Requires(results.Measurement == this);

            Contract.Ensures(ResultsCDC != null);

            if (ResultsCDC != null)
            {
                ResultsCDC.ScoresUpdated -= Results_ScoresUpdated;
            }

            ResultsCDC = results;

            ResultsCDC.ScoresUpdated += Results_ScoresUpdated;
        }

        internal void AddWHOResults(ResultsWHO results)
        {
            Contract.Requires(results != null);
            Contract.Requires(results.Measurement == this);

            Contract.Ensures(ResultsWHO != null);

            if (ResultsWHO != null)
            {
                ResultsWHO.ScoresUpdated -= Results_ScoresUpdated;
            }

            ResultsWHO = results;

            ResultsWHO.ScoresUpdated += Results_ScoresUpdated;
        }

        private void Results_ScoresUpdated(object sender, EventArgs e)
        {
            if (ScoresUpdated != null)
            {
                ScoresUpdated(this, new EventArgs());
            }
        }

        internal void RecalculateScores()
        {
            if (ResultsCDC != null)
            {
                ResultsCDC.CalculateScores();
            }

            if (ResultsWHO != null)
            {
                ResultsWHO.CalculateScores();
            }
        }

        internal void RecalculateAge()
        {
            Contract.Requires(Person != null);

            if (Person.DateBirth.HasValue)
            {
                TimeSpan ts = DateMeasurement - Person.DateBirth.Value;
                AgeDays = ts.TotalDays;
            }
        }

        public void CalculateBodyMassIndex()
        {
            BodyMassIndex = null;

            if (Height.HasValue && Weight.HasValue)
            {
                double height = Height.Value;
                double weight = Weight.Value;

                if (IsRecumbent && AgeMonths >= 24)
                {
                    // remove 0.7 cm from height
                    height = height - 0.7;
                }
                else if (!IsRecumbent && AgeMonths < 24)
                {
                    // add 0.7 cm to height
                    height = height + 0.7;
                }


                BodyMassIndex = weight / ((height / 100) * (height / 100));
            }
        }
        #endregion // Methods
    }
}
